🎖️GitЯра🎖️
Node / meshtastic / Meshtastic-Android / files / androidApp / src / google / kotlin / org / meshtastic / app / map / offline / terrain / HillshadeTileProvider.kt
Displaying Raw • Download
androidApp/src/google/kotlin/org/meshtastic/app/map/offline/terrain/HillshadeTileProvider.kt renovate/fastlane-2.x-lockfile (056eed64) Text, 6.96 KB
T8b949e/*
* Copyright (c) 2026 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
Tff7b72package T7ee787org.meshtastic.app.map.offline.terrain
Tff7b72import T7ee787android.graphics.Bitmap
Tff7b72import T7ee787co.touchlab.kermit.Logger
Tff7b72import T7ee787com.google.android.gms.maps.model.Tile
Tff7b72import T7ee787com.google.android.gms.maps.model.TileProvider
Tff7b72import T7ee787org.meshtastic.feature.map.terrain.ElevationTile
Tff7b72import T7ee787org.meshtastic.feature.map.terrain.Hillshade
Tff7b72import T7ee787org.meshtastic.feature.map.terrain.TerrainSource
Tff7b72import T7ee787org.meshtastic.feature.map.terrain.TerrainTileStore
Tff7b72import T7ee787org.meshtastic.feature.map.terrain.TileIndex
Tff7b72import T7ee787org.meshtastic.feature.map.terrain.decodeTerrariumTile
Tff7b72import T7ee787java.io.ByteArrayOutputStream
Tff7b72import T7ee787kotlin.math.roundToInt
Tff7b72private Tff7b72val Te6edf3LOG Tff7b72= Te6edf3LoggerTb4b4b4.Te6edf3withTagTb4b4b4(Ta5d6ff"Ta5d6ffHillshadeTileProviderTa5d6ff"Tb4b4b4)
T8b949e/**
* Pre-rendered hillshade tiles for the Google flavor: shadow-alpha PNGs decoded from [store]'s downloaded Terrarium
* tiles, following the same shape as [org.meshtastic.app.map.MBTilesProvider] and
* [org.meshtastic.app.map.tiles.RasterTileProvider] — Google's [TileProvider.getTile] is a synchronous callback the
* Maps SDK already invokes off the main thread, so this does its decode+shade+encode work inline rather than returning
* a `Deferred`/`Flow`; there's nowhere to hand async work off to that the SDK would wait on anyway.
*
* Each output pixel is black RGB with shadow depth as alpha, matching [Hillshade.shade]'s own contract: it composites
* correctly over any basemap without a separate light/dark palette. [Hillshade.shade] needs a margin ring of real
* neighbor-tile elevation data around the tile it shades ([ElevationStitcher] assembles that, gracefully clamping to
* this tile's own edge wherever a neighbor wasn't downloaded — see its own doc comment).
*
* [getTile] is called concurrently by the Maps SDK from multiple threads, so both caches below are guarded by
* `synchronized`, as is [org.meshtastic.app.map.offline.pmtiles.OfflineVectorRenderer]'s identically-shaped tile cache.
*/
Tff7b72internal Tff7b72class T56d364HillshadeTileProviderTb4b4b4(Tff7b72private Tff7b72val Te6edf3storeTb4b4b4: Te6edf3TerrainTileStoreTb4b4b4) Tb4b4b4: Te6edf3TileProvider Tb4b4b4{
Tff7b72private Tff7b72val Te6edf3elevationCache Tff7b72=
Tff7b72object Tb4b4b4: T56d364LinkedHashMapTff7b72<Tffa657StringTb4b4b4, Te6edf3ElevationTileTff7b72>Tb4b4b4(Te6edf3INITIAL_CAPACITYTb4b4b4, Te6edf3LOAD_FACTORTb4b4b4, Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72override Tff7b72fun Td2a8ffremoveEldestEntryTb4b4b4(Te6edf3eldestTb4b4b4: Te6edf3MutableMapTb4b4b4.Te6edf3MutableEntryTff7b72<Tffa657StringTb4b4b4, Te6edf3ElevationTileTff7b72>Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3size Tff7b72> Te6edf3MAX_CACHED_ELEVATION_TILES
Tb4b4b4}
Tff7b72private Tff7b72val Te6edf3renderedCache Tff7b72=
Tff7b72object Tb4b4b4: T56d364LinkedHashMapTff7b72<Tffa657StringTb4b4b4, Te6edf3TileTff7b72>Tb4b4b4(Te6edf3INITIAL_CAPACITYTb4b4b4, Te6edf3LOAD_FACTORTb4b4b4, Tff7b72trueTb4b4b4) Tb4b4b4{
Tff7b72override Tff7b72fun Td2a8ffremoveEldestEntryTb4b4b4(Te6edf3eldestTb4b4b4: Te6edf3MutableMapTb4b4b4.Te6edf3MutableEntryTff7b72<Tffa657StringTb4b4b4, Te6edf3TileTff7b72>Tb4b4b4)Tb4b4b4: Tffa657Boolean Tff7b72=
Te6edf3size Tff7b72> Te6edf3MAX_CACHED_RENDERED_TILES
Tb4b4b4}
Tff7b72override Tff7b72fun Td2a8ffgetTileTb4b4b4(Te6edf3xTb4b4b4: Tffa657IntTb4b4b4, Te6edf3yTb4b4b4: Tffa657IntTb4b4b4, Te6edf3zoomTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3Tile? Tb4b4b4{
Tff7b72val Te6edf3key Tff7b72= Ta5d6ff"Tffd700$Te6edf3zoomTa5d6ff/Tffd700$Te6edf3xTa5d6ff/Tffd700$Te6edf3yTa5d6ff"
Tff7b72val Te6edf3cached Tff7b72= Te6edf3synchronizedTb4b4b4(Te6edf3renderedCacheTb4b4b4) Tb4b4b4{ Te6edf3renderedCacheTff7b72[Te6edf3keyTff7b72] Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3cached Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tff7b72return Te6edf3cached
Tff7b72val Te6edf3source Tff7b72= Te6edf3terrainSourceForZoomTb4b4b4(Te6edf3zoomTb4b4b4)
Tff7b72val Te6edf3center Tff7b72= Te6edf3decodeTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3TileIndexTb4b4b4(Te6edf3zoomTb4b4b4, Te6edf3xTb4b4b4, Te6edf3yTb4b4b4)Tb4b4b4)
Tff7b72return Tff7b72if Tb4b4b4(Te6edf3center Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3TileProviderTb4b4b4.Te6edf3NO_TILE
Tb4b4b4} Tff7b72else Tb4b4b4{
Tff7b72val Te6edf3neighbors Tff7b72=
Te6edf3NEIGHBOR_OFFSETSTb4b4b4.Te6edf3mapNotNull Tb4b4b4{ Tb4b4b4(Te6edf3dxTb4b4b4, Te6edf3dyTb4b4b4) Tff7b72-Tff7b72>
Te6edf3decodeTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3TileIndexTb4b4b4(Te6edf3zoomTb4b4b4, Te6edf3x Tff7b72+ Te6edf3dxTb4b4b4, Te6edf3y Tff7b72+ Te6edf3dyTb4b4b4)Tb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Tb4b4b4(Te6edf3dx Te6edf3to Te6edf3dyTb4b4b4) Te6edf3to Tffa657it Tb4b4b4}
Tb4b4b4}
Tb4b4b4.Te6edf3toMapTb4b4b4(Tb4b4b4)
Tff7b72val Te6edf3padded Tff7b72= Te6edf3ElevationStitcherTb4b4b4.Te6edf3buildPaddedTb4b4b4(Te6edf3centerTb4b4b4, Te6edf3neighborsTb4b4b4)
Tff7b72val Te6edf3shadow Tff7b72= Te6edf3HillshadeTb4b4b4.Te6edf3shadeTb4b4b4(Te6edf3paddedTb4b4b4, Te6edf3centerTb4b4b4.Te6edf3widthTb4b4b4, Te6edf3centerTb4b4b4.Te6edf3heightTb4b4b4, Te6edf3MAX_SHADOW_ALPHATb4b4b4)
Tff7b72val Te6edf3tile Tff7b72= Te6edf3TileTb4b4b4(Te6edf3centerTb4b4b4.Te6edf3widthTb4b4b4, Te6edf3centerTb4b4b4.Te6edf3heightTb4b4b4, Te6edf3renderShadowPngTb4b4b4(Te6edf3shadowTb4b4b4, Te6edf3centerTb4b4b4.Te6edf3widthTb4b4b4, Te6edf3centerTb4b4b4.Te6edf3heightTb4b4b4)Tb4b4b4)
Te6edf3synchronizedTb4b4b4(Te6edf3renderedCacheTb4b4b4) Tb4b4b4{ Te6edf3renderedCacheTff7b72[Te6edf3keyTff7b72] Tff7b72= Te6edf3tile Tb4b4b4}
Te6edf3tile
Tb4b4b4}
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffdecodeTb4b4b4(Te6edf3sourceTb4b4b4: Te6edf3TerrainSourceTb4b4b4, Te6edf3tileTb4b4b4: Te6edf3TileIndexTb4b4b4)Tb4b4b4: Te6edf3ElevationTile? Tb4b4b4{
Tff7b72val Te6edf3key Tff7b72= Ta5d6ff"Tffd700${Te6edf3sourceTb4b4b4.Te6edf3dirNameTffd700}Ta5d6ff/Tffd700${Te6edf3tileTb4b4b4.Te6edf3zoomTffd700}Ta5d6ff/Tffd700${Te6edf3tileTb4b4b4.Te6edf3xTffd700}Ta5d6ff/Tffd700${Te6edf3tileTb4b4b4.Te6edf3yTffd700}Ta5d6ff"
Tff7b72val Te6edf3cached Tff7b72= Te6edf3synchronizedTb4b4b4(Te6edf3elevationCacheTb4b4b4) Tb4b4b4{ Te6edf3elevationCacheTff7b72[Te6edf3keyTff7b72] Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3cached Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tff7b72return Te6edf3cached
Tff7b72val Te6edf3decoded Tff7b72= Te6edf3storeTb4b4b4.Te6edf3readTileTb4b4b4(Te6edf3sourceTb4b4b4, Te6edf3tileTb4b4b4)Tff7b72?.Te6edf3let Tb4b4b4{ Te6edf3bytes Tff7b72-Tff7b72> Te6edf3decodeSafelyTb4b4b4(Te6edf3bytesTb4b4b4) Tb4b4b4}
Tff7b72if Tb4b4b4(Te6edf3decoded Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Te6edf3synchronizedTb4b4b4(Te6edf3elevationCacheTb4b4b4) Tb4b4b4{ Te6edf3elevationCacheTff7b72[Te6edf3keyTff7b72] Tff7b72= Te6edf3decoded Tb4b4b4}
Tff7b72return Te6edf3decoded
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffdecodeSafelyTb4b4b4(Te6edf3bytesTb4b4b4: Te6edf3ByteArrayTb4b4b4)Tb4b4b4: Te6edf3ElevationTile? Tff7b72= Tff7b72try Tb4b4b4{
Te6edf3decodeTerrariumTileTb4b4b4(Te6edf3bytesTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3IllegalStateExceptionTb4b4b4) Tb4b4b4{
T8b949e// No tile coordinates in the message: zoom/x/y is location-adjacent data (AGENTS.md Privacy First).
Te6edf3LOGTb4b4b4.Te6edf3wTb4b4b4(Te6edf3eTb4b4b4) Tb4b4b4{ Ta5d6ff"Ta5d6ffCould not decode a downloaded Terrarium tileTa5d6ff" Tb4b4b4}
Tff7b72null
Tb4b4b4}
Tff7b72private Tff7b72fun Td2a8ffrenderShadowPngTb4b4b4(Te6edf3shadowTb4b4b4: Te6edf3FloatArrayTb4b4b4, Te6edf3widthTb4b4b4: Tffa657IntTb4b4b4, Te6edf3heightTb4b4b4: Tffa657IntTb4b4b4)Tb4b4b4: Te6edf3ByteArray Tb4b4b4{
T8b949e// RGB is always black (0,0,0), so premultiplied-vs-straight alpha makes no difference to the pixels
T8b949e// encoded here (0 times any alpha is still 0) — unlike ElevationTile's own Terrarium decode, which must
T8b949e// disable premultiplication because its elevation data actually lives in non-zero RGB channels.
Tff7b72val Te6edf3bitmap Tff7b72= Te6edf3BitmapTb4b4b4.Te6edf3createBitmapTb4b4b4(Te6edf3widthTb4b4b4, Te6edf3heightTb4b4b4, Te6edf3BitmapTb4b4b4.Te6edf3ConfigTb4b4b4.Te6edf3ARGB_8888Tb4b4b4)
Tff7b72val Te6edf3pixels Tff7b72= Te6edf3IntArrayTb4b4b4(Te6edf3width Tff7b72* Te6edf3heightTb4b4b4)
Tff7b72for Tb4b4b4(Te6edf3i Tff7b72in Te6edf3shadowTb4b4b4.Te6edf3indicesTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3alpha Tff7b72= Tb4b4b4(Te6edf3shadowTff7b72[Te6edf3iTff7b72] Tff7b72* Te6edf3BYTE_MAXTb4b4b4)Tb4b4b4.Te6edf3roundToIntTb4b4b4(Tb4b4b4)Tb4b4b4.Te6edf3coerceInTb4b4b4(T79c0ff0Tb4b4b4, Te6edf3BYTE_MAXTb4b4b4.Te6edf3toIntTb4b4b4(Tb4b4b4)Tb4b4b4)
Te6edf3pixelsTff7b72[Te6edf3iTff7b72] Tff7b72= Te6edf3alpha Te6edf3shl Te6edf3ALPHA_SHIFT
Tb4b4b4}
Te6edf3bitmapTb4b4b4.Te6edf3setPixelsTb4b4b4(Te6edf3pixelsTb4b4b4, T79c0ff0Tb4b4b4, Te6edf3widthTb4b4b4, T79c0ff0Tb4b4b4, T79c0ff0Tb4b4b4, Te6edf3widthTb4b4b4, Te6edf3heightTb4b4b4)
Tff7b72val Te6edf3stream Tff7b72= Te6edf3ByteArrayOutputStreamTb4b4b4(Tb4b4b4)
Te6edf3bitmapTb4b4b4.Te6edf3compressTb4b4b4(Te6edf3BitmapTb4b4b4.Te6edf3CompressFormatTb4b4b4.Te6edf3PNGTb4b4b4, Te6edf3PNG_QUALITYTb4b4b4, Te6edf3streamTb4b4b4)
Te6edf3bitmapTb4b4b4.Te6edf3recycleTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3streamTb4b4b4.Te6edf3toByteArrayTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tff7b72internal Tff7b72companion Tff7b72object Tb4b4b4{
T8b949e/**
* Fixed shadow strength, matching [Hillshade]'s own fixed light source (azimuth/altitude aren't configurable
* either). Not tuned against real on-device rendering — no display was available in this environment; revisit
* alongside [Hillshade]'s own `DESPIKE_THRESHOLD_METERS` once one is.
*/
Tff7b72const Tff7b72val Te6edf3MAX_SHADOW_ALPHA Tff7b72= T79c0ff0.5f
Tff7b72private Tff7b72val Te6edf3NEIGHBOR_OFFSETS Tff7b72= Te6edf3listOfTb4b4b4(Tff7b72-T79c0ff1 Te6edf3to Tff7b72-T79c0ff1Tb4b4b4, T79c0ff0 Te6edf3to Tff7b72-T79c0ff1Tb4b4b4, T79c0ff1 Te6edf3to Tff7b72-T79c0ff1Tb4b4b4, Tff7b72-T79c0ff1 Te6edf3to T79c0ff0Tb4b4b4, T79c0ff1 Te6edf3to T79c0ff0Tb4b4b4, Tff7b72-T79c0ff1 Te6edf3to T79c0ff1Tb4b4b4, T79c0ff0 Te6edf3to T79c0ff1Tb4b4b4, T79c0ff1 Te6edf3to T79c0ff1Tb4b4b4)
Tff7b72private Tff7b72const Tff7b72val Te6edf3MAX_CACHED_ELEVATION_TILES Tff7b72= T79c0ff1T79c0ff2T79c0ff8
Tff7b72private Tff7b72const Tff7b72val Te6edf3MAX_CACHED_RENDERED_TILES Tff7b72= T79c0ff6T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3INITIAL_CAPACITY Tff7b72= T79c0ff1T79c0ff6
Tff7b72private Tff7b72const Tff7b72val Te6edf3LOAD_FACTOR Tff7b72= T79c0ff0.75f
Tff7b72private Tff7b72const Tff7b72val Te6edf3BYTE_MAX Tff7b72= T79c0ff2T79c0ff5T79c0ff5f
Tff7b72private Tff7b72const Tff7b72val Te6edf3ALPHA_SHIFT Tff7b72= T79c0ff2T79c0ff4
Tff7b72private Tff7b72const Tff7b72val Te6edf3PNG_QUALITY Tff7b72= T79c0ff1T79c0ff0T79c0ff0
Tb4b4b4}
Tb4b4b4}
Served by rngit 1.5.4 - Generated in 0.04s